package org.zoomdev.zoom.web.parameter;

import org.zoomdev.zoom.aop.Constructor;
import org.zoomdev.zoom.aop.Setter;
import org.zoomdev.zoom.aop.factory.ConstructorFactory;
import org.zoomdev.zoom.aop.factory.SetterFactory;
import org.zoomdev.zoom.common.caster.Caster;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.utils.CachedClasses;
import org.zoomdev.zoom.common.utils.Classes;
import org.zoomdev.zoom.validator.SimpleValidator;
import org.zoomdev.zoom.validator.Validators;
import org.zoomdev.zoom.web.action.ActionContext;
import org.zoomdev.zoom.web.annotations.Param;
import org.zoomdev.zoom.web.parameter.parser.impl.DirectParameterAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public final class ParameterAdapterFactoryManager {


    public static final DirectParameterAdapter DIRECT_ADAPTER = new DirectParameterAdapter();

    private static final ActionContextToMap PRE_TO_MAP = new ActionContextToMap();
    private static final SessionAdapter SESSION = new SessionAdapter();
    private static final RequestAdapter REQUEST = new RequestAdapter();
    private static final ResponseAdapter RESPONSE = new ResponseAdapter();
    private static final ActionContextAdapter ACTION_CONTEXT = new ActionContextAdapter();

    private static final PathParameterAdapter PATH_PARAMETER = new PathParameterAdapter();

    private static final AutoAdaptTypeParameterAdapter AUTO_ADAPT_TYPE_ADAPTER = new AutoAdaptTypeParameterAdapter();
    private static final PreParameterAdapter PRE_PARAMETER = new PreParameterAdapter();
    private static final RawPreParameterAdapter RAW_PRE_PARAMETER = new RawPreParameterAdapter();
    private final List<ParameterAdapterFactory> factories = new ArrayList<>();

    public void add(ParameterAdapterFactory factory) {
        factories.add(factory);
    }

    public void insert(ParameterAdapterFactory factory, int index) {
        factories.add(index, factory);
    }

    public ParameterAdapter createParameterAdapter(String name, Type type, AnnotatedElement element) {
        /**
         * 先返回用户自定义的
         */
        for (ParameterAdapterFactory factory : factories) {
            ParameterAdapter adapter = factory.createParameterAdapter(name, type, element);
            if (adapter != null) {
                return adapter;
            }
        }

        /**
         * 返回系统定义的
         */
        return getAdapter(name, type, element);
    }

    private static boolean isRequestBody(String name, AnnotatedElement element) {
        if (element.isAnnotationPresent(Param.class)) {
            Param param = element.getAnnotation(Param.class);
            if (param.body()) {
                return true;
            }
        }
        return false;
    }


    private static boolean isPathVariable(String name, AnnotatedElement element) {
        if (element.isAnnotationPresent(Param.class)) {
            Param param = element.getAnnotation(Param.class);
            if (param.name().startsWith("{") && param.name().endsWith("}")) {
                return true;
            }
            if (param.pathVariable()) {
                return true;
            }
        }
        return false;
    }

    private ParameterAdapter getAdapter(String name, Type type, AnnotatedElement element) {

        if (type instanceof Class) {
            Class<?> classOfParameter = (Class<?>) type;
            if (HttpServletRequest.class.isAssignableFrom(classOfParameter)) {
                return REQUEST;
            }
            if (HttpServletResponse.class.isAssignableFrom(classOfParameter)) {
                return RESPONSE;
            }
            if (ActionContext.class.isAssignableFrom(classOfParameter)) {
                return ACTION_CONTEXT;
            }
            if (HttpSession.class.isAssignableFrom(classOfParameter)) {
                return SESSION;
            }

            if (PreParameter.class.isAssignableFrom(classOfParameter)) {
                return PRE_PARAMETER;
            }

        }

        if (isPathVariable(name, element)) {
            //简单类型直接来
            return createValidatedParameterAdapter(name, type, element);
        }

        if (isRequestBody(name, element)) {
            if (type instanceof Class) {
                Class<?> clazz = (Class<?>) type;
                if (clazz == Object.class) {
                    return RAW_PRE_PARAMETER;
                }
                if (Classes.isSimple(clazz) || clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) {
                    throw new ZoomException(type + "无法创建body类型的Parameter");
                }

                if (clazz == Map.class) {
                    return PRE_TO_MAP;
                }

                Field[] fields = CachedClasses.getFields(clazz);

                ParameterAdapter[] adapters = new ParameterAdapter[fields.length];
                int index = 0;
                for (Field field : fields) {
                    //需要检测吗 Validators.createValidators(field);
                    ParameterAdapter adapter = createParameterAdapter(field.getName(), field.getGenericType(), field);
                    adapters[index++] = adapter;
                }
                return new BeanRequestBodyAdapter(clazz, fields, adapters);
            }
        }


        return createNormalAdapter(name, type, element);
    }

    private static ParameterAdapter createValidatedParameterAdapter(String name, Type type, AnnotatedElement element) {
        Object defaultValue = Validators.getDefaultValue(type, element);
        SimpleValidator validator = Validators.createValidator(name, type, element);
        if (defaultValue == null && validator == null) {
            return PATH_PARAMETER;
        } else {
            return new ValidatPathParameterAdapter(defaultValue, validator);
        }
    }

    private static ParameterAdapter createNormalAdapter(String name, Type type, AnnotatedElement element) {
        Object defaultValue = Validators.getDefaultValue(type, element);
        SimpleValidator validator = Validators.createValidator(name, type, element);
        if (defaultValue == null && validator == null) {
            return AUTO_ADAPT_TYPE_ADAPTER;
        } else {
            return new ValidateNormalAdapter(defaultValue, validator);
        }
    }

    private static final class ActionContextToMap implements ParameterAdapter {
        @Override
        public Object get(ActionContext context, String name, Type type) {
            return context.getPreParameter().getMap();
        }
    }

    private static class ValidateNormalAdapter implements ParameterAdapter {
        private final Object defaultValue;
        private final SimpleValidator validator;

        private ValidateNormalAdapter(Object defaultValue, SimpleValidator validator) {
            this.defaultValue = defaultValue;
            this.validator = validator;
        }

        @Override
        public Object get(ActionContext context, String name, Type type) {
            PreParameter parameter = context.getPreParameter();
            Object value = parameter.get(name);

            // Apply default value if exists
            if (value == null && defaultValue != null) {
                value = defaultValue;
                //parameter.(name, value); // Update request with default value
                return Caster.toType(value, type);
            }

            // Validate the value if validator exists
            if (validator != null) {
                validator.validate(value);
            }

            return Caster.toType(value, type);
        }
    }

    private static class ValidatPathParameterAdapter implements ParameterAdapter {
        private final Object defaultValue;
        private final SimpleValidator validator;

        private ValidatPathParameterAdapter(Object defaultValue, SimpleValidator validator) {
            this.defaultValue = defaultValue;
            this.validator = validator;
        }

        @Override
        public Object get(ActionContext context, String name, Type type) {
            HttpServletRequest request = context.getRequest();
            Object value = request.getAttribute(name);

            // Apply default value if exists
            if (value == null && defaultValue != null) {
                value = defaultValue;
                request.setAttribute(name, value); // Update request with default value
                return Caster.toType(value, type);
            }

            // Validate the value if validator exists
            if (validator != null) {
                validator.validate(value);
            }

            return Caster.toType(value, type);
        }
    }

    private static class AutoAdaptTypeParameterAdapter implements ParameterAdapter {

        @Override
        public Object get(ActionContext context, String name, Type type) {
            PreParameter map = context.getPreParameter();
            Object value = map.get(name);
            return Caster.toType(value, type);
        }
    }


    private static class ValidateProperty implements ParameterAdapter {

        @Override
        public Object get(ActionContext context, String name, Type type) {
            return null;
        }
    }


    private static class RawPreParameterAdapter implements ParameterAdapter {

        @Override
        public Object get(ActionContext context, String name, Type type) {
            return context.getPreParameter().getRaw();
        }
    }

    private static class PreParameterAdapter implements ParameterAdapter {
        @Override
        public Object get(ActionContext context, String name, Type type) {
            return context.getPreParameter();
        }
    }


    private static class SessionAdapter implements ParameterAdapter {

        @Override
        public Object get(ActionContext context, String name, Type type) {

            return context.getRequest().getSession();
        }

    }


    private static class RequestAdapter implements ParameterAdapter {

        @Override
        public Object get(ActionContext context, String name, Type type) {

            return context.getRequest();
        }

    }


    private static class ResponseAdapter implements ParameterAdapter {

        @Override
        public Object get(ActionContext context, String name, Type type) {

            return context.getResponse();
        }

    }


    private static class ActionContextAdapter implements ParameterAdapter {

        @Override
        public Object get(ActionContext context, String name, Type type) {
            return context;
        }

    }


    private static class BeanRequestBodyAdapter implements ParameterAdapter {
        private final ParameterAdapter[] adapters;

        private final Constructor constructor;

        private final Type[] types;

        private final Field[] fields;
        private final String[] names;

        private final Setter[] setters;

        BeanRequestBodyAdapter(Class<?> type, Field[] fields, ParameterAdapter[] adapters) {
            this.adapters = adapters;
            this.constructor = ConstructorFactory.FACTORY.create(type);
            this.setters = SetterFactory.create(fields);
            int c = fields.length;
            this.fields = fields;
            this.names = new String[c];
            this.types = new Type[c];
            for (int i = 0; i < c; ++i) {
                types[i] = fields[i].getGenericType();
                names[i] = fields[i].getName();
            }
        }

        @Override
        public Object get(ActionContext context, String name, Type type) {
            Object data = constructor.newInstance();
            int c = adapters.length;
            for (int i = 0; i < c; ++i) {
                Object value = adapters[i].get(context, names[i], types[i]);
                setters[i].set(data, value);
            }
            return data;
        }
    }

    private static class PathParameterAdapter implements ParameterAdapter {

        public PathParameterAdapter() {

        }

        @Override
        public Object get(ActionContext context, String name, Type type) {
            return Caster.toType(context.getRequest().getAttribute(name), type);
        }

    }


}
